/*
 * DefaultInjector.java
 *
 * Created on 2008/06/08 +0900
 *
 * $Revision: 58 $
 * $Date: 2008-12-06 08:59:45 +0000 (Sat, 06 Dec 2008) $
 */

package org.gikolet.framework.inject;

import java.util.Hashtable;
import java.util.NoSuchElementException;
import java.util.Vector;

import org.gikolet.framework.util.Assert;

/**
 * @author Tetsutaro (latest modification by $Author: tetsutaro.ss $)
 * @version $Revision: 58 $ $Date: 2008-12-06 08:59:45 +0000 (Sat, 06 Dec 2008) $
 */
public class DefaultInjector implements Injector {
    private Hashtable bindTable;

    public DefaultInjector(Moduleable[] moduleables) {
	Assert.isNotNull(moduleables);

	this.bindTable = new Hashtable();

	DefaultBinder defaultBinder = new DefaultBinder();
	for (int i = 0; i < moduleables.length; i++) {
	    if (moduleables[i] != null) {
		moduleables[i].configure(defaultBinder);
	    }
	}

	defaultBinder.build();

	InstanceIterator iter = getInstanceIterator(Serviceable.class);
	while (iter.hasNext()) {
	    ((Serviceable) iter.next()).service(this);
	}
    }

    private void putBindTable(Object key, Provider provider) {
	Assert.isNotNull(key);
	Assert.isNotNull(provider);

	Object obj = this.bindTable.get(key);
	if (obj == null) {
	    this.bindTable.put(key, provider);
	} else {
	    Vector v;
	    if (obj instanceof Vector) {
		v = (Vector) obj;
	    } else {
		v = new Vector();
		v.addElement(obj);
		this.bindTable.put(key, v);
	    }
	    v.addElement(provider);
	}
    }

    private Provider[] getBindTable(Object key) {
	Assert.isNotNull(key);

	Provider[] providers;
	Object obj = this.bindTable.get(key);
	if (obj == null) {
	    providers = new Provider[0];
	} else if (obj instanceof Vector) {
	    Vector v = (Vector) obj;
	    providers = new Provider[v.size()];
	    v.copyInto(providers);
	} else {
	    providers = new Provider[1];
	    providers[0] = (Provider) obj;
	}
	return providers;
    }

    class DefaultBinder implements Binder {
	private Vector bindBuilder = new Vector();

	public LinkedBindingBuilder bind(Class clazz) {
	    Assert.isNotNull(clazz);

	    DefaultBindingBuilder dbb = new DefaultBindingBuilder(clazz.getName());
	    this.bindBuilder.addElement(dbb);

	    return dbb;
	}

	public LinkedBindingBuilder bind(String key) {
	    Assert.isNotNull(key);
	    Assert.isNotEmpty(key);

	    DefaultBindingBuilder dbb = new DefaultBindingBuilder(key);
	    this.bindBuilder.addElement(dbb);

	    return dbb;
	}

	void build() {
	    for (int i = 0; i < bindBuilder.size(); i++) {
		DefaultBindingBuilder dbb = (DefaultBindingBuilder) bindBuilder
			.elementAt(i);
		if (dbb.toProvider == null) {
		    throw new InjectException("Provider is not found.");
		}
		putBindTable(dbb.bindKey, dbb.toScope.scope(dbb.bindKey, dbb.toProvider));
	    }
	}
    }

    static class DefaultBindingBuilder implements LinkedBindingBuilder {
	private String bindKey;
	private Provider toProvider;
	private Scope toScope = Scope.PROTOTYPE;

	DefaultBindingBuilder(String key) {
	    this.bindKey = key;
	}

	public ScopedBinderBuilder to(final Class clazz) {
	    Assert.isNotNull(clazz);

	    this.toProvider = new Provider() {
		public Object get() {
		    try {
			return clazz.newInstance();
		    } catch (Exception e) {
			e.printStackTrace();
			throw new InjectException();
		    }
		}
	    };

	    return this;
	}

	public void toInstance(final Object instance) {
	    Assert.isNotNull(instance);

	    this.toProvider = new Provider() {
		public Object get() {
		    return instance;
		}
	    };
	    this.toScope = Scope.PROTOTYPE;
	}

	public ScopedBinderBuilder toProvider(Provider provider) {
	    this.toProvider = provider;

	    return this;
	}

	public void in(Scope scope) {
	    if (scope == null) {
		scope = Scope.PROTOTYPE;
	    }
	    this.toScope = scope;
	}
    }

    public Object getInstance(Class clazz) {
	Assert.isNotNull(clazz);

	return getInstance(clazz.getName());
    }

    public Object getInstance(String key) throws InjectException {
	Assert.isNotNull(key);

	Provider[] providers = getBindTable(key);
	if (providers.length > 0) {
	    Object obj = providers[0].get();
	    if (obj != null) {
		return obj;
	    }
	}
	throw new InjectException("key[" + key + "] is not found.");
    }

    public InstanceIterator getInstanceIterator(Class clazz) {
	Assert.isNotNull(clazz);

	return getInstanceIterator(clazz.getName());
    }

    public InstanceIterator getInstanceIterator(String key) {
	final Provider[] providers = getBindTable(key);

	return new InstanceIterator() {
	    private int index = 0;

	    public boolean hasNext() {
		return providers.length > this.index;
	    }

	    public Object next() throws NoSuchElementException {
		if (!hasNext()) {
		    throw new NoSuchElementException();
		}
		return providers[this.index++].get();
	    }
	};
    }
}
